package simulation;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import linker.ClientLinker;
import linker.ServerLinker;
import linker.exception.CanNotReadObjectException;
import linker.exception.CanNotWriteObjectException;
import linker.exception.ErrorClosingSocketException;
import linker.exception.ErrorCreatingClientSocketException;
import linker.exception.ErrorCreatingServerSocketException;
import smartfleet.communication.carrwprotocol.CarInfo;
import smartfleet.communication.carrwprotocol.CarLoginResponse;
import smartfleet.communication.carrwprotocol.CarSubscribe;
import smartfleet.communication.carrwprotocol.CarUnsubscribe;
import smartfleet.communication.carrwprotocol.CarUpdate;
import smartfleet.communication.carrwprotocol.CarUpdateResponse;
import smartfleet.communication.smartfleetobjects.GeneralInfo;
import smartfleet.communication.stationrwprotocol.StationInfo;
import smartfleet.communication.stationrwprotocol.StationLoginResponse;

public class RWServer {
	
	private static int					serialId	= 1;
	
	private Map<Integer, GeneralInfo>	carsReg;
	
	private Map<Integer, GeneralInfo>	carsSub;
	
	private ServerLinker				linker;
	
	private Map<Integer, GeneralInfo>	stations;
	
	public RWServer() throws ErrorCreatingServerSocketException {

		this.linker = new ServerLinker(6780);
		this.setCarsReg(new HashMap<Integer, GeneralInfo>());
		this.setCarsSub(new HashMap<Integer, GeneralInfo>());
		this.stations = new HashMap<Integer, GeneralInfo>();
		
	}
	
	private Map<Integer, GeneralInfo> getStationsAtDistance(int lat, int lon, Map<Integer, GeneralInfo> stations) {

		Map<Integer, GeneralInfo> result = new HashMap<Integer, GeneralInfo>();
		
		Iterator iterator = stations.keySet().iterator();
		
		while (iterator.hasNext()) {
			int key = (Integer) iterator.next();
			GeneralInfo info = stations.get(key);
			int lat2 = info.getGeoLat();
			int lon2 = info.getGeoLong();
			
			lat /= 0.000009;
			lon /= 0.000011;
			
			lat2 /= 0.000009;
			lon2 /= 0.000011;
			
			double dist = Math.sqrt(Math.pow((lat - lat2), 2) + Math.pow((lon - lon2), 2));
			
			if (dist <= 300)
				result.put(key, info);
		}
		
		return result;
	}
	
	public Map<Integer, GeneralInfo> getCarsReg() {

		return this.carsReg;
	}
	
	public Map<Integer, GeneralInfo> getCarsSub() {

		return this.carsSub;
	}
	
	public void run() {

		Object receivedObj = null;
		
		while (true) {
			
			ClientLinker client;
			try {
				client = this.linker.acceptNewClient();
				
				System.out.println("New client accepted.");
				
				try {
					receivedObj = client.readObject();
					// Thread worker = new Thread(new parseRequest(receivedObj, this.carsReg, this.stations));
				} catch (CanNotReadObjectException e1) {
					e1.printStackTrace();
					try {
						client.close();
					} catch (ErrorClosingSocketException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					continue;
				}
				
				if (receivedObj instanceof CarInfo) {
					System.out.println("Logging car.");
					CarLoginResponse login;
					GeneralInfo info = null;
					if (!this.stations.isEmpty()) {
						Iterator iterator = this.stations.keySet().iterator();
						while (iterator.hasNext()) {
							int key = (Integer) iterator.next();
							if (this.stations.get(key).getGeoLat() == ((CarInfo) receivedObj).getGeoLat() && this.stations.get(key).getGeoLong() == ((CarInfo) receivedObj).getGeoLong())
								info = this.stations.get(key);
						}
					}
					
					String ip = ((CarInfo) receivedObj).getIp();
					int port = ((CarInfo) receivedObj).getPort();
					int lat = ((CarInfo) receivedObj).getGeoLat();
					int lon = ((CarInfo) receivedObj).getGeoLong();
					int id = serialId++;
					CarInfo newinfo = new CarInfo(id, lat, lon, ip, port);
					this.carsReg.put(id, newinfo);
					login = new CarLoginResponse(id, info);
					try {
						client.writeObject(login);
					} catch (CanNotWriteObjectException e) {
						e.printStackTrace();
						try {
							client.close();
						} catch (ErrorClosingSocketException e1) {
							// TODO Auto-generated catch block
							e1.printStackTrace();
						}
						continue;
					}
					System.out.println("CAR: Your ip is " + ip + " and your port is " + port);
				}
				if (receivedObj instanceof CarSubscribe) {
					System.out.println("Subscribing car.");
					int id = ((CarSubscribe) receivedObj).getId();
					int lat = ((CarSubscribe) receivedObj).getGeoLat();
					int lon = ((CarSubscribe) receivedObj).getGeoLong();
					CarInfo newinfo = new CarInfo(id, lat, lon, this.getCarsReg().get(id).getIp(), this.carsReg.get(id).getPort());
					this.carsSub.put(id, newinfo);
					Map<Integer, GeneralInfo> station = getStationsAtDistance(lat, lon, this.stations);
					CarUpdateResponse resp = new CarUpdateResponse(id, null, station);
					try {
						client.writeObject(resp);
					} catch (CanNotWriteObjectException e) {
						e.printStackTrace();
						try {
							client.close();
						} catch (ErrorClosingSocketException e1) {
							// TODO Auto-generated catch block
							e1.printStackTrace();
						}
						continue;
					}
					
				}
				if (receivedObj instanceof CarUnsubscribe) {
					System.out.println("Unsubscribing car.");
					int id = ((CarUnsubscribe) receivedObj).getId();
					this.carsSub.remove(id);
				}
				if (receivedObj instanceof CarUpdate) {
					System.out.println("Updating car.");
					int id = ((CarUpdate) receivedObj).getId();
					int lat = ((CarUpdate) receivedObj).getGeoLat();
					int lon = ((CarUpdate) receivedObj).getGeoLong();
					this.carsSub.get(id).setGeoLat(lat);
					this.carsSub.get(id).setGeoLong(lon);
					Map<Integer, GeneralInfo> station = getStationsAtDistance(lat, lon, this.stations);
					CarUpdateResponse resp = new CarUpdateResponse(id, null, station);
					try {
						client.writeObject(resp);
					} catch (CanNotWriteObjectException e) {
						e.printStackTrace();
						try {
							client.close();
						} catch (ErrorClosingSocketException e1) {
							// TODO Auto-generated catch block
							e1.printStackTrace();
						}
						continue;
					}
				}
				if (receivedObj instanceof StationInfo) {
					System.out.println("Logging station.");
					int id = serialId++;
					String ip = ((StationInfo) receivedObj).getIp();
					int port = ((StationInfo) receivedObj).getPort();
					int lat = ((StationInfo) receivedObj).getGeoLat();
					int lon = ((StationInfo) receivedObj).getGeoLong();
					StationInfo newinfo = new StationInfo(id, lat, lon, ip, port);
					this.stations.put(id, newinfo);
					StationLoginResponse login = new StationLoginResponse(id, null);
					try {
						client.writeObject(login);
					} catch (CanNotWriteObjectException e) {
						e.printStackTrace();
						try {
							client.close();
						} catch (ErrorClosingSocketException e1) {
							// TODO Auto-generated catch block
							e1.printStackTrace();
						}
						continue;
					}
					System.out.println("Station: Your ip is " + ip + " and your port is " + port);
					
				}
				
				client.close();
				System.out.println("Client socket closed");
			} catch (ErrorCreatingClientSocketException e2) {
				// TODO Auto-generated catch block
				e2.printStackTrace();
			} catch (ErrorClosingSocketException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
	public void setCarsReg(Map<Integer, GeneralInfo> carsReg) {

		this.carsReg = carsReg;
	}
	
	public void setCarsSub(Map<Integer, GeneralInfo> carsSub) {

		this.carsSub = carsSub;
	}
	
	/** @param args */
	public static void main(String[] args) {

		try {
			new RWServer().run();
		} catch (ErrorCreatingServerSocketException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			System.exit(1);
		}
		
	}
	
}
